#ifndef UUID_AF14E3F05D094FEB5AC13F977AAEE616
#define UUID_AF14E3F05D094FEB5AC13F977AAEE616

extern "C"
{
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "luajit.h"
#include "luaext.h"
}

#include <gvl/debug.hpp>

inline void get_metatable(lua_State* L, void* tag)
{
	lua_pushlightuserdata(L, tag);
	lua_rawget(L, LUA_REGISTRYINDEX);
}

// ..upvalues.. -- mt
inline void make_metatable(lua_State* L, void* tag, luaL_Reg* functions, int nup)
{
	lua_newtable(L); // ..upvalues.. mt
	lua_insert(L, -1-nup); // mt ..upvalues..
	luaL_openlib(L, 0, functions, nup); // mt
	lua_pushlightuserdata(L, tag);
	lua_pushvalue(L, -2); // mt tag -- mt tag mt
	lua_rawset(L, LUA_REGISTRYINDEX); // mt tag mt -- mt
}

template<typename T>
inline T& check_object(lua_State* L, int idx, void* tag, char const* extramsg)
{
	luaL_argcheck(L, luaext_hasuserdatatag(L, idx, tag), idx, extramsg);
	void* p = lua_touserdata(L, idx);
	return *static_cast<T*>(p);
}

#define LUA_CHECK_FUNC(type, name, tag) \
inline type& check_##name(lua_State* L, int idx) { \
	return check_object<type>(L, idx, &tag, "expected " #name); \
}

template<typename T>
inline T* try_object(lua_State* L, int idx, void* tag)
{
	if(!luaext_hasuserdatatag(L, idx, tag))
		return 0;
	void* p = lua_touserdata(L, idx);
	return static_cast<T*>(p);
}

// object -- object
inline void prepare_object(lua_State* L, void* tag)
{
	luaext_setuserdatatag(L, -1, tag);

	get_metatable(L, tag); // self -- self mt
	lua_setmetatable(L, -2); // self mt -- self
}

#define LUA_NEW_OBJ(type, name, tag, params) \
	void* name##_mem = lua_newuserdata(L, sizeof(type)); \
	type* self = new (name##_mem) type params; \
	prepare_object(L, &tag)

struct lua_object
{
	lua_State* _lua_L;
	int _lua_id;

	lua_object()
	: _lua_L(0)
	{
	}

	~lua_object()
	{
		if(_lua_L)
			luaL_unref(_lua_L, LUA_REGISTRYINDEX, _lua_id);
	}

	void detach()
	{
		if(_lua_L)
		{
			luaL_unref(_lua_L, LUA_REGISTRYINDEX, _lua_id);
			_lua_L = 0;
		}
	}

	void attach(lua_State* L)
	{
		_lua_L = L;
		_lua_id = luaL_ref(L, LUA_REGISTRYINDEX);
	}

	lua_State* L()
	{ return _lua_L; }

	void get()
	{
		sassert(_lua_L);
		lua_rawgeti(_lua_L, LUA_REGISTRYINDEX, _lua_id);
	}
};

#endif // UUID_AF14E3F05D094FEB5AC13F977AAEE616
